package scheduler

import (
	"sync"
	"sync/atomic"
	"time"

	"gitee.com/MikeDDD/gu/guutil"
	"gitee.com/MikeDDD/gu/logs"
)

var (
	schedulers = new(sync.Map)
	ticker     = time.NewTicker(time.Second)
	startTimes = int32(0)
)

func Start() {
	if !atomic.CompareAndSwapInt32(&startTimes, 0, 1) { // not swapped
		logs.Warn("repeat call Start()")
		return
	}

	guutil.GoWithRecover(func() {
		for range ticker.C {
			now := Now() // 注意：偏移之后的时间

			schedulers.Range(func(key, value any) bool {
				if value.(*scheduler).exec(now) {
					schedulers.Delete(key)
				}

				return true
			})
		}
	})
}

type scheduler struct {
	name           string // 唯一标识
	startAt        int64  // 从什么时候起
	duration       int64  // 间隔多久执行
	repeatTimes    int32  // 执行多少次，-1:无限
	triggeredTimes int32  // 已经执行多少次
	callback       func() // 回调函数
	callbackState  int32  // 回调函数执行状态
}

// return 是否删除该定时器
func (s *scheduler) exec(now int64) (exit bool) {
	if s.startAt+s.duration*int64(s.triggeredTimes) > now {
		return false // 未到时间
	}

	if atomic.CompareAndSwapInt32(&s.callbackState, 0, 1) {
		// 当上一个 callback 未完成时，跳过
		guutil.GoWithRecover(func() {
			defer atomic.StoreInt32(&s.callbackState, 0)

			s.callback()
		})
	} else {
		logs.Warn("scheduler %v callback() execute time exceed duration %v", s.name, s.duration)
	}

	s.triggeredTimes++

	if s.repeatTimes == -1 {
		logs.Debug("exec %v, next exec at %v", s.name, s.startAt+s.duration*int64(s.triggeredTimes))
	} else {
		if s.triggeredTimes >= s.repeatTimes {
			logs.Debug("exec %v, remain times is zero, will destory", s.name)
			return true
		} else {
			logs.Debug("exec %v, next exec at %v, remain times %v", s.name, s.startAt+s.duration*int64(s.triggeredTimes), s.repeatTimes-s.triggeredTimes)
		}
	}

	return false
}

func newScheduler(name string, startAt int64, duration int64, times int32, triggeredTimes int32, callback func()) {
	if times == 0 {
		logs.Error("shceduler %v times is zero", name)
		return
	}

	s := &scheduler{
		name:           name,
		startAt:        startAt,
		duration:       duration,
		repeatTimes:    times,
		triggeredTimes: triggeredTimes,
		callback:       callback,
	}

	schedulers.Store(name, s)

	logs.Debug("scheduler registed: %v", s)
}
